using System;
using System.Collections.Generic;
using MBT.Utils;
using System.Reflection;

namespace MBT.Modeling
{
    /// <summary>
    /// Information about methods and procedures with their -actual- parameters. It will be used in transitions of test model.
    /// <remarks>Why we don't use MethodInfo here? Because it is about actual stuff (like value of parameters) not formal stuff</remarks>
    /// </summary>
    public class ActionInfo
    {
        List<object> FlatParameters;
        byte id = 0;


        #region -=[ Internal structure ]=-
        /// <summary>
        /// This is a mapper to reduce amount of space required for storing information about events (their IDs, and their parameters).
        /// </summary>
        internal class IDInfoKeeper
        {
            /// <summary>
            /// Keep info of Actions
            /// </summary>
            class a_inf
            {
                public int numberOfParams = 0;
                public bool isControllable = true;
                public string type = "void";
                public int id = 0;

                public a_inf(int numberOfParams, bool Controllable, string type, int id)
                {
                    this.isControllable = Controllable;
                    this.type = type;
                    this.numberOfParams = numberOfParams;
                    this.id = id;
                }
            }

            /// <summary>
            /// Keep info of Parameters
            /// </summary>
            class p_inf
            {
                public string type = "void";
                public int id = 0;

                public p_inf(string type, int id)
                {
                    this.type = type;
                    this.id = id;
                }
            }

            static Dictionary<string, a_inf> actionMap = new Dictionary<string, a_inf>();
            static Dictionary<string, p_inf> paramMap = new Dictionary<string, p_inf>();


            public static bool addAction(string actionID, bool isControllable, string type)
            {
                if (!actionMap.ContainsKey(actionID))
                {
                    actionMap.Add(actionID, new a_inf(0, isControllable, type, actionMap.Count));
                    addParam(actionID, "result", type);
                    return true;
                }
                return false;
            }

            public static bool addParam(string actionID, string Param, string type)
            {
                if (actionMap.ContainsKey(actionID))
                {
                    paramMap.Add(actionID + " " + Param, new p_inf(type, actionMap[actionID].numberOfParams));
                    actionMap[actionID].numberOfParams++;
                    return true;
                }
                return false;
            }


            public static int NumberOfParams(string actionID)
            {
                if (actionMap.ContainsKey(actionID))
                    return actionMap[actionID].numberOfParams;
                else
                    return -1;
            }
            public static void Clear()
            {
                paramMap.Clear();
                actionMap.Clear();
            }
            public static string getActionID(int id)
            {
                foreach (KeyValuePair<string, a_inf> kvp in actionMap)
                {
                    if (kvp.Value.id == id)
                        return kvp.Key;
                }
                return "";
            }
            public static bool isControllable(string actionID)
            {
                if (actionMap.ContainsKey(actionID))
                    return actionMap[actionID].isControllable;
                return false;
            }

            public static bool isControllable(int id)
            {
                return actionMap[getActionID(id)].isControllable;
            }

            public static string getParamID(string actionID, int id)
            {
                foreach (KeyValuePair<string, p_inf> kvp in paramMap)
                {
                    if (kvp.Value.id == id)
                    {
                        string temp = kvp.Key;
                        return temp.Remove(0, kvp.Key.IndexOf(" "));
                    }
                }
                return "";
            }

            public static int getActionNumber(string id)
            {
                if (actionMap.ContainsKey(id))
                    return actionMap[id].id;
                else
                    return -1;
            }

            public static int getParamNumber(string actionID, string paramID)
            {
                string id = actionID + " " + paramID;
                if (paramMap.ContainsKey(id))
                    return paramMap[id].id;
                else
                    return -1;
            }
        }
        #endregion

        #region -=[ Constructors ]=-
        public ActionInfo(MethodInfo inf)
        {
            string ID = inf.Name;
            int mid = IDInfoKeeper.getActionNumber(ID);
            if (mid == -1)
                throw new Exception("Unknown method!");

            FlatParameters = new List<object>();
            id = (byte)mid;
            for (int i = 0; i < IDInfoKeeper.NumberOfParams(ID); i++)
            {
                FlatParameters.Add(null);
            }
        }

        public ActionInfo(string ID)
        {
            int mid = IDInfoKeeper.getActionNumber(ID);
            if (mid == -1)
                throw new Exception("Unknown method!");

            FlatParameters = new List<object>();
            id = (byte)mid;
            for (int i = 0; i < IDInfoKeeper.NumberOfParams(ID); i++)
            {
                FlatParameters.Add(null);
            }
        }

        public ActionInfo(List<object> parameters)
        {
            FlatParameters = parameters;
        }

        public ActionInfo(string ID, params object[] parameter)
        {
            int mid = IDInfoKeeper.getActionNumber(ID);
            if (mid == -1)
                throw new Exception("Unknown method!");
            int nop = IDInfoKeeper.NumberOfParams(ID);
            if (nop != parameter.Length)
                throw new Exception("Wrong number of parameters for method " + ID + "!");
            FlatParameters = new List<object>();
            id = (byte)mid;
            for (int i = 0; i < nop; i++)
            {
                FlatParameters.Add(parameter[i]);
            }
        }

        #endregion

        /// <summary>
        /// ID of the action
        /// </summary>
        public string ID
        {
            get
            {
                return IDInfoKeeper.getActionID(id);
            }
        }

        /// <summary>
        /// Access to value of parameters by their name
        /// </summary>
        /// <param name="paramID">name of parameter</param>
        /// <returns>actual value of the parameter</returns>
        public object this[string paramID]
        {
            get
            {
                return FlatParameters[IDInfoKeeper.getParamNumber(ID, paramID)];
            }
            set
            {
                FlatParameters[IDInfoKeeper.getParamNumber(ID, paramID)] = value;
            }
        }

        /// <summary>
        /// Access to value of parameters by their index
        /// </summary>
        /// <param name="i">index of parameter</param>
        /// <returns>actual value of the parameter</returns>
        public object this[int i]
        {
            get
            { return FlatParameters[i]; }
            set
            { FlatParameters[i] = value; }
        }

        /// <summary>
        /// Number of parameters
        /// </summary>
        public int NumberOfArguments
        {
            get
            { return FlatParameters.Count; }
        }

        /// <summary>
        /// Check wheather this action is controllable or observable
        /// </summary>
        public bool isControllable
        {
            get
            { return IDInfoKeeper.isControllable(id); }
        }

        /// <summary>
        /// Convert the actionInfo to string
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            string str = "(";
            for (int i = 0; i < FlatParameters.Count; i++)
            {
                if (FlatParameters[i] == null)
                    str += IDInfoKeeper.getParamID(IDInfoKeeper.getActionID(id), i) + "=null ";
                else
                    str += (i > 0 ? "," : "") + FlatParameters[i].ToString();
            }
            return str + ")";
        }

        /// <summary>
        /// Check that two ActionInfo objects are equal or not
        /// </summary>
        /// <param name="dataOfEnums">the other object</param>
        /// <returns>true is both are equal, false in other case</returns>
        public override bool Equals(object obj)
        {
            if (!(obj is ActionInfo))
                return false;
            ActionInfo inf2 = ((ActionInfo)obj);
            if (ID != inf2.ID)
                return false;
            if (FlatParameters.Count != inf2.FlatParameters.Count)
                return false;
            for (int i = 0; i < FlatParameters.Count; i++)
            {
                if (FlatParameters[i] == null && inf2[i] == null)
                    continue;
                else if (FlatParameters[i] == null || inf2[i] == null)
                    return false;
                else if (!(FlatParameters[i].Equals(inf2.FlatParameters[i])))
                    return false;
            }
            return true;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        /// <summary>
        /// Clone the action info
        /// </summary>
        /// <returns>a clone of this</returns>
        public ActionInfo Clone()
        {
            ActionInfo a = new ActionInfo(IDInfoKeeper.getActionID(id));
            for (int i = 0; i < FlatParameters.Count; i++)
            {
                a.FlatParameters[i] = FlatParameters[i];
            }
            return a;
        }
    }
}
